/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_provisioning_softap.h"

#include <stdlib.h>
#include <string.h>

#include "ola_base_utils.h"
#include "ola_coap_net.h"
#include "ola_data.h"
#include "ola_device_model.h"
#include "ola_def.h"
#include "ola_error.h"
#include "ola_log.h"
#include "ola_negotiation.h"
#include "ola_pal_os.h"
#include "ola_pal_network.h"
#include "ola_sdk_api.h"
#include "ola_sdk.h"
#include "ola_random.h"
#include "ola_register.h"
#include "securec.h"
#include "cJSON.h"

#define MAX_SOFTAP_SSID_LEN 32
#define MIN_SOFTAP_SSID_LEN 21

#define OLA_SSID_PREFIX "OLA"
#define OLA_SSID_VERSION "1"
#define OLA_PREFIX_VERSION_LEN 4
#define MAX_SOFTAP_SSID_DEV_LEN (MIN_SOFTAP_SSID_LEN - OLA_PREFIX_VERSION_LEN)

#define LAST_DIGITS_SN_LEN 3
#define SSID_ITEM_EE_LEN 2
#define SSID_ITEM_FF_LEN 2
#define MAX_CUSTOM_SSID_DATA_LEN 12
#define MIN_CUSTOM_SSID_DATA_LEN 1

#define DEVICE_PROVISIONING_COAP_TIMEVAL (1 * 50)
#define MAX_RELEASE_COAP_SOURCES_RETRY_TIMES 10
#define TIMEOUT_SLEEP_MS 1000
#define MS_PER_SECOND 1000

/* 设备加密协商能力掩码 */
/* 加密协商类型 */
#define SEC_CAP_MASK_NEGO_TYPE_JPAKE 0x02
#define SEC_CAP_MASK_NEGO_TYPE_PECDH 0x04
/* 认证方式 */
#define SEC_CAP_MASK_AUTH_TYPE_LISENCE 0x08
/* 默认PIN码 */
#define SEC_CAP_MASK_DEFAULT_PIN_CODE 0x10
/* 使用PIN码或者QR码 */
#define SEC_CAP_MASK_CODE_TYPE_PIN 0x20
#define SEC_CAP_MASK_CODE_TYPE_QR 0x40
#define SEC_CAP_MASK_CODE_TYPE_BOTH 0x60

typedef enum {
    OLA_SDK_SOFTAP_PRO_STATE_INIT,
    OLA_SDK_SOFTAP_PRO_STATE_WORKING,
    OLA_SDK_SOFTAP_PRO_STATE_WORKDONE,
    OLA_SDK_SOFTAP_PRO_STATE_TIMEOUT,
    OLA_SDK_SOFTAP_PRO_STATE_FAILED,
} OlaSoftapProState;

static const OlaNegotiationHook *g_negoHook = NULL;
static OlaCoapContext *g_provisioningCtx = NULL;
static OlaSoftapProState g_provisioningState = OLA_SDK_SOFTAP_PRO_STATE_INIT;
static unsigned long long g_proStartTime = 0;
static unsigned long long g_proTimeout = 0;
static bool g_isSoftapProRunning = false;
static unsigned int g_taskWaitTime = 50;

static OlaCoapSession *g_negoSession = NULL;
static OlaReqId g_reqId;

void TaskWait(void)
{
    OlaSleepMs(g_taskWaitTime);
}

static void OlaSetProvisioningState(OlaSoftapProState state)
{
    OlaLogInfo("set provisioning state[%d]", state);
    g_provisioningState = state;
}

static OlaSoftapProState OlaGetProvisioningState(void)
{
    return g_provisioningState;
}

static int GetDefaultPinCode(OlaData *outData)
{
#define DEFAULT_PINCODE "12345678"
    if ((outData == NULL) || (outData->data != NULL)) {
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }
    unsigned int dataLen = strlen(DEFAULT_PINCODE) + 1;
    outData->data = (unsigned char *)OlaMalloc(dataLen);
    if (outData->data == NULL) {
        OlaLogError("malloc failed, len[%u]", dataLen);
        return OLA_OS_ERR_MALLOC;
    }
    (void)memset_s(outData->data, dataLen, 0, dataLen);
    if (memcpy_s(outData->data, dataLen, DEFAULT_PINCODE, strlen(DEFAULT_PINCODE)) != OLA_OK) {
        OlaLogError("copy failed");
        return OLA_SECUREC_ERR_MEMCOPY;
    }
    outData->len = strlen(DEFAULT_PINCODE);
    return OLA_OK;
}

static int GetPinCode(unsigned char *pinCode, unsigned int *len)
{
    if ((pinCode == NULL) || (len == NULL) || (*len == 0)) {
        return OLA_ERROR;
    }

    OlaData pinCodeData = {NULL, 0};

    const OlaNetCfgParam *netcfgParam = OlaGetNetCfgParam();
    if (netcfgParam == NULL) {
        OlaLogError("get netcfg param failed");
        return OLA_ERROR;
    }

    int ret;
    if (netcfgParam->pinCodeType == OLA_PINCODE_TYPE_OUTSIDE) {
        ret = OlaGetDeviceData(OLA_DATA_TYPE_NETCFG_PINCODE, &pinCodeData);
    } else {
        ret = GetDefaultPinCode(&pinCodeData);
    }
    OlaLogInfo("pincode type[%d], ret[%d]", netcfgParam->pinCodeType, ret);
    if (ret != OLA_OK) {
        return ret;
    }

    if ((pinCodeData.data == NULL) || (pinCodeData.len == 0)) {
        OlaLogError("invalid pinCodeData, len[%u]", pinCodeData.len);
        OlaDataFree(&pinCodeData);
        return OLA_ERROR;
    }
    if (pinCodeData.len > *len) {
        OlaLogError("invliad code, maxLen[%u], getLen[%u]", *len, pinCodeData.len);
        OlaDataFree(&pinCodeData);
        return OLA_ERROR;
    }

    if (memcpy_s(pinCode, *len, pinCodeData.data, pinCodeData.len) != OLA_OK) {
        OlaLogError("memcpy pincode failed");
        OlaDataFree(&pinCodeData);
        return OLA_SECUREC_ERR_MEMCOPY;
    }

    *len = pinCodeData.len;
    OlaDataFree(&pinCodeData);

    return OLA_OK;
}

static int NegoSendData(const OlaData *sendData)
{
    OlaLogDebug("nego send data in");
    if ((sendData == NULL) || (sendData->data == NULL) || (sendData->len == 0)) {
        OlaLogError("nego send data failed");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }
    if (g_negoSession == NULL) {
        OlaLogError("nego session null");
        return OLA_ERROR;
    }
    OlaCoapParam param = {
        .reqId = g_reqId,
        .type = OLA_COAP_PACKET_TYPE_RESPONSE,
    };
    int ret = OlaCoapSendData(g_negoSession, sendData, &param);
    if (ret != OLA_OK) {
        OlaLogError("nego sendData failed, ret[%d]", ret);
        return OLA_ERROR;
    }
    return OLA_OK;
}

static OlaNegotiationCb g_negoCb = {
    .getPinCodeCb = GetPinCode,
    .sendData = NegoSendData,
};

static int BuildCoapResource(OlaCoapResource *resource, const char *uri, OlaCoapMethodType method,
    OlaCoapRequestHandler handler)
{
    if ((resource == NULL) || (uri == NULL)) {
        OlaLogError("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }
    if (memcpy_s(resource->uri, sizeof(resource->uri), uri, strlen(uri)) != OLA_OK) {
        OlaLogError("memcpy uri failed");
        return OLA_SECUREC_ERR_MEMCOPY;
    }
    resource->method = method;
    resource->handler = handler;
    resource->next = NULL;
    return OLA_OK;
}

/* 安全协商数据处理回调函数 */
static int NegoDataHandler(OlaCoapSession *session, const OlaCoapPacket *pkt, OlaData *outData, OlaReqId reqId)
{
    OlaLogDebug("negotiation: data handle func in");
    if ((session == NULL) || (pkt == NULL) || (outData == NULL)) {
        OlaLogError("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }
    if (g_negoHook == NULL) {
        OlaLogError("nego hook null");
        return OLA_NEGO_ERR_HOOK_NULL;
    }

#ifdef SUPPORT_SPEKE_3
    /* 如果为speke协商，则通过SendData接口返回，保存session以及reqId */
    const OlaNetCfgParam *netcfgParam = OlaGetNetCfgParam();
    if (netcfgParam == NULL) {
        OlaLogError("get netcfg param failed");
        return OLA_ERROR;
    }
    if (netcfgParam->negoType == OLA_SEC_NEGO_TYPE_SPEKE) {
        OlaLogInfo("nego type speke");
        g_negoSession = session;
        g_reqId = reqId;
    }
#endif

    return g_negoHook->processData(&(pkt->payload), outData);
}

/* 添加协商资源 */
static int SetNegoServicesHandler(void)
{
    /* 获取协商方式 */
    const OlaNetCfgParam *initParam = OlaGetNetCfgParam();
    if (initParam == NULL) {
        OlaLogError("get init param failed");
        return OLA_ERROR;
    }
    OlaSecNegotiationType negoType = initParam->negoType;
    OlaLogInfo("nego type %d", negoType);
    char *uri = NULL;
    int ret;
    /* 根据不同的协商方式添加对应的资源 */
    if (negoType == OLA_SEC_NEGO_TYPE_SPEKE) {
        OlaCoapResource negoStart = {0};
        OlaCoapResource negoFinish = {0};
        ret = BuildCoapResource(&negoStart, OLA_COAP_URI_SPEKE_START, OLA_COAP_METHOD_TYPE_POST, NegoDataHandler);
        ret |= BuildCoapResource(&negoFinish, OLA_COAP_URI_SPEKE_FINISH, OLA_COAP_METHOD_TYPE_POST, NegoDataHandler);
        if (ret != OLA_OK) {
            OlaLogError("build speke coap resource failed");
            return OLA_ERROR;
        }
        if ((OlaCoapRegisterResource(g_provisioningCtx, &negoStart) != OLA_OK) ||
            (OlaCoapRegisterResource(g_provisioningCtx, &negoFinish) != OLA_OK)) {
            OlaLogError("add coap resource failed");
            return OLA_ERROR;
        }
        return OLA_OK;
    } else if (negoType == OLA_SEC_NEGO_TYPE_JPAKE) {
        uri = OLA_COAP_URI_JPAKE_URI;
    } else if (negoType == OLA_SEC_NEGO_TYPE_PECDH) {
        uri = OLA_COAP_URI_PECDH_URI;
    } else {
        OlaLogError("invalid nego type");
        return OLA_ERROR;
    }

    OlaCoapResource resource = {0};
    ret = BuildCoapResource(&resource, uri, OLA_COAP_METHOD_TYPE_POST, NegoDataHandler);
    if (ret != OLA_OK) {
        OlaLogError("build nego coap resource failed, ret[%d]", ret);
        return ret;
    }
    if (OlaCoapRegisterResource(g_provisioningCtx, &resource) != OLA_OK) {
        OlaLogError("add coap resource failed");
        return OLA_ERROR;
    }
    return OLA_OK;
}

static int OlaSetRegisterInfo(const char *cloudUrl, const char *regCode, const char *ticket, const char *psk)
{
    if ((cloudUrl == NULL) || (regCode == NULL)) {
        OlaLogError("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    OlaData regCodeData = {(unsigned char *)regCode, strlen(regCode)};
    if (OlaSetData(OLA_DATA_TYPE_REGISTER_CODE, &regCodeData) != OLA_OK) {
        OlaLogError("set reg code failed");
        return OLA_ERROR;
    }

    if (OlaRegisterSetRegistryCode((unsigned char *)regCode) != OLA_OK) {
        OlaLogError("set reg code to register failed");
        return OLA_ERROR;
    }

    OlaData urlData = {(unsigned char *)cloudUrl, strlen(cloudUrl)};
    if (OlaSetData(OLA_DATA_TYPE_CLOUD_URL, &urlData) != OLA_OK) {
        OlaLogError("set cloud url failed");
        return OLA_ERROR;
    }

    if (ticket != NULL) {
        OlaData ticketData = {(unsigned char *)ticket, strlen(ticket)};
        if (OlaSetData(OLA_DATA_TYPE_TICKET, &ticketData) != OLA_OK) {
            OlaLogError("set ticket failed");
            return OLA_ERROR;
        }
    }

    if (psk != NULL) {
        OlaData pskData = {(unsigned char *)psk, strlen(psk)};
        if (OlaSetData(OLA_DATA_TYPE_PSK, &pskData) != OLA_OK) {
            OlaLogError("set psk failed");
            return OLA_ERROR;
        }
    }

    return OLA_OK;
}

#if defined(SUPPORT_SPEKE_3) || defined(SUPPORT_SPEKE_1)
static int ParseProDataForSpeke(const OlaData *data)
{
    cJSON *inJson = cJSON_Parse((const char*)data->data);
    if (inJson == NULL) {
        OlaLogError("parse data as json failed");
        return OLA_CJSON_ERR_PARSE;
    }
#if defined(SUPPORT_SPEKE_3)
    cJSON *dataJson = cJSON_GetObjectItem(inJson, "data");
#elif defined(SUPPORT_SPEKE_1)
    cJSON *dataJson = inJson;
#endif
    if (dataJson == NULL) {
        OlaLogError("parse data as json failed");
        return OLA_CJSON_ERR_PARSE;
    }
    int ret;
    do {
        char *ssid = OlaGetJsonString(dataJson, OLA_STRING_SSID);
        char *pwd = OlaGetJsonString(dataJson, OLA_STRING_PASSWORD);
        char *cloudUrl = OlaGetJsonString(dataJson, OLA_STRING_CLOUD_URL);
        char *registryCode = OlaGetJsonString(dataJson, OLA_STRING_REGISTRY_CODE);
        char *ticket = OlaGetJsonString(dataJson, OLA_STRING_TICKET);
        char *psk = OlaGetJsonString(dataJson, OLA_STRING_PSK);
        if ((ssid == NULL) || (pwd == NULL) || (cloudUrl == NULL) || (registryCode == NULL)) {
            OlaLogError("parse provisioning data item failed");
            ret = OLA_CJSON_ERR_GET_STRING;
            break;
        }

        ret = OlaSetWifiInfo(ssid, strlen(ssid), pwd, strlen(pwd));
        if (ret != OLA_OK) {
            OlaLogError("set wifi info failed, ret[%d]", ret);
            break;
        }

        /* TODO 当前speke不存储注册信息 */
        ret = OlaSetRegisterInfo(cloudUrl, registryCode, ticket, psk);
        if (ret != OLA_OK) {
            OlaLogError("set register info failed, ret[%d]", ret);
            ret = OLA_OK;
        }
    } while (0);

    /* 使用完WiFi信息后, 及时清除密码 */
    OlaClearJsonString(dataJson);
    cJSON_Delete(inJson);
    return ret;
}
#endif

static int ParseProvisioningData(const OlaData *data)
{
    if ((data == NULL) || (data->data == NULL) || (data->len == 0)) {
        OlaLogError("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

#if defined(SUPPORT_SPEKE_3) || defined(SUPPORT_SPEKE_1)
    /* speke协商当前有额外的data层级且无ticket，故打桩 */
    const OlaNetCfgParam *param = OlaGetNetCfgParam();
    if (param != NULL && param->negoType == OLA_SEC_NEGO_TYPE_SPEKE) {
        return ParseProDataForSpeke(data);
    }
#endif

    cJSON *dataJson = cJSON_Parse((const char*)data->data);
    if (dataJson == NULL) {
        OlaLogError("parse data as json failed");
        return OLA_CJSON_ERR_PARSE;
    }

    int ret;
    do {
        char *ssid = OlaGetJsonString(dataJson, OLA_STRING_SSID);
        char *pwd = OlaGetJsonString(dataJson, OLA_STRING_PASSWORD);
        char *cloudUrl = OlaGetJsonString(dataJson, OLA_STRING_CLOUD_URL);
        char *registryCode = OlaGetJsonString(dataJson, OLA_STRING_REGISTRY_CODE);
        char *ticket = OlaGetJsonString(dataJson, OLA_STRING_TICKET);
        char *psk = OlaGetJsonString(dataJson, OLA_STRING_PSK);
        if ((ssid == NULL) || (pwd == NULL) || (cloudUrl == NULL) || (registryCode == NULL)) {
            OlaLogError("parse provisioning data item failed");
            ret = OLA_CJSON_ERR_GET_STRING;
            break;
        }

        ret = OlaSetWifiInfo(ssid, strlen(ssid), pwd, strlen(pwd));
        if (ret != OLA_OK) {
            OlaLogError("set wifi info failed, ret[%d]", ret);
            break;
        }

        ret = OlaSetRegisterInfo(cloudUrl, registryCode, ticket, psk);
        if (ret != OLA_OK) {
            OlaLogError("set register info failed, ret[%d]", ret);
            break;
        }
    } while (0);

    /* 使用完WiFi信息后, 及时清除密码 */
    OlaClearJsonString(dataJson);
    cJSON_Delete(dataJson);
    return ret;
}

static int DecodeProvisioningData(const OlaData *inData, OlaData *decodeData)
{
    if (g_negoHook == NULL) {
        OlaLogError("nego hook null");
        return OLA_NEGO_ERR_HOOK_NULL;
    }

    if (g_negoHook->decodeData(inData, decodeData) != OLA_OK) {
        OlaLogError("provisioning data decode failed");
        return OLA_NEGO_ERR_DECRYPT_DATA;
    }
    return OLA_OK;
}

static int SaveProvisioningData(const OlaData *proData)
{
    if (!OlaIsRegister()) {
        if (ParseProvisioningData(proData) != OLA_OK) {
            OlaLogError("parse provisioning data failed");
            return OLA_ERROR;
        }
    } else {
        OlaLogInfo("device is registered");
    }

    return OLA_OK;
}

#define INT_MAX_LEN 10
#define JSON_ERRCODE_STR "{\"errcode\":%d}"

static int BuildProvisioningRsp(int ret, OlaData *outData)
{
    unsigned int len = strlen(JSON_ERRCODE_STR) + INT_MAX_LEN + 1;
    char *rspBuf = (char *)OlaMalloc(len);
    if (rspBuf == NULL) {
        OlaLogError("malloc rsp buf failed, len[%u]", len);
        return OLA_ERROR;
    }

    (void)memset_s(rspBuf, len, 0, len);

    if (snprintf_s(rspBuf, len, len - 1, JSON_ERRCODE_STR, ret) < 0) {
        OlaFree(rspBuf);
        OlaLogError("snprintf_s\r\n");
        return OLA_ERROR;
    }

    OlaData encData = {(unsigned char *)rspBuf, strlen(rspBuf)};
    if (g_negoHook->encodeData(&encData, outData) != OLA_OK) {
        OlaLogWarning("nego data encode failed");
        OlaFree(rspBuf);
        return OLA_NEGO_ERR_ENCRYPT_DATA;
    }

    OlaFree(rspBuf);

    return OLA_OK;
}

/* 配网数据处理回调函数 */
static int ProvisioningDataHandler(OlaCoapSession *session, const OlaCoapPacket *pkt, OlaData *outData, OlaReqId reqId)
{
    OlaLogDebug("function in");
    if ((session == NULL) || (pkt == NULL) || (pkt->payload.data == NULL) ||
        (pkt->payload.len == 0) || (outData == NULL) || (outData->data != NULL)) {
        OlaLogError("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    OlaData decodeData = {NULL, 0};
    int ret;
    do {
        // 解密数据
        ret = DecodeProvisioningData(&(pkt->payload), &decodeData);
        if (ret != OLA_OK) {
            OlaLogError("data decode failed, ret[%d]", ret);
            break;
        }

        // 处理数据
        ret = SaveProvisioningData(&decodeData);
        if (ret != OLA_OK) {
            OlaLogError("save data failed, ret[%d]", ret);
            break;
        }
    } while (0);

    OlaDataFree(&decodeData);

    ret = BuildProvisioningRsp(ret, outData);
    if (ret != OLA_OK) {
        OlaLogError("rsp data failed, ret[%d]", ret);
        return ret;
    }

    OlaSetProvisioningState(OLA_SDK_SOFTAP_PRO_STATE_WORKDONE);

    OlaLogInfo("provisioning data handler, ret[%d]", ret);
    /* 处理完毕后结束回话，此回复是最后一条回复 */
    return OLA_COAP_RESP_LAST_MSG;
}

/* 设置coap服务各个uri的处理回调 */
static int SetProServicesHandler(void)
{
    /* 添加协商处理资源 */
    if (SetNegoServicesHandler() != OLA_OK) {
        OlaLogError("add coap nego resource failed");
        return OLA_ERROR;
    }
    OlaCoapResource proResource = {0};
    int ret = BuildCoapResource(&proResource, OLA_COAP_URI_PROVISIONING,
        OLA_COAP_METHOD_TYPE_POST, ProvisioningDataHandler);
    if (ret != OLA_OK) {
        OlaLogError("build provisioning coap resource failed, ret[%d]", ret);
        return ret;
    }
    if (OlaCoapRegisterResource(g_provisioningCtx, &proResource) != OLA_OK) {
        OlaLogError("add coap resource failed");
        return OLA_ERROR;
    }
    return OLA_OK;
}

static int BuildSsidWithDevInfoInner(char *ssid, unsigned int len, unsigned int *offset,
    const OlaDeviceInfo *devInfo)
{
    if ((ssid == NULL) || (len == 0) || (offset == NULL) || (devInfo == NULL)) {
        OlaLogError("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    unsigned int remainLen = len - *offset;
    unsigned int tmpOffset = *offset;
    char *buf = ssid + (*offset);

    /* 拷贝厂家名称 */
    if ((strlen(devInfo->manuName) != OLA_DEV_MODEL_MANU_NAME_LEN) ||
        strcpy_s(buf, remainLen, devInfo->manuName) != OLA_OK) {
        OlaLogError("copy namu name failed");
        return OLA_ERROR;
    }
    remainLen -= OLA_DEV_MODEL_MANU_NAME_LEN;
    tmpOffset += OLA_DEV_MODEL_MANU_NAME_LEN;
    buf += OLA_DEV_MODEL_MANU_NAME_LEN;

    /* 拷贝typeId */
    if ((strlen(devInfo->catID) != OLA_DEV_MODEL_CATID_LEN)||
        (strcpy_s(buf, remainLen, devInfo->catID) != OLA_OK)) {
        OlaLogError("copy cat Id failed");
        return OLA_ERROR;
    }
    remainLen -= OLA_DEV_MODEL_CATID_LEN;
    tmpOffset += OLA_DEV_MODEL_CATID_LEN;
    buf += OLA_DEV_MODEL_CATID_LEN;

    /* 拷贝序列号后三位 */
    if (strlen(devInfo->sn) < LAST_DIGITS_SN_LEN) {
        OlaLogError("sn too short");
        return OLA_ERROR;
    }
    const char *lastDigitsOfSN = devInfo->sn + strlen(devInfo->sn) - LAST_DIGITS_SN_LEN;
    if (strcpy_s(buf, remainLen, lastDigitsOfSN) != OLA_OK) {
        OlaLogError("copy last digits of sn failed");
        return OLA_SECUREC_ERR_STRCPY;
    }
    remainLen -= LAST_DIGITS_SN_LEN;
    tmpOffset += LAST_DIGITS_SN_LEN;
    buf += LAST_DIGITS_SN_LEN;

    /* 拷贝productId */
    if ((strlen(devInfo->prodId) != OLA_DEV_MODEL_PRODID_LEN)||
        (strcpy_s(buf, remainLen, devInfo->prodId) != OLA_OK)) {
        OlaLogError("copy prodId failed");
        return OLA_ERROR;
    }
    remainLen -= OLA_DEV_MODEL_PRODID_LEN;
    tmpOffset += OLA_DEV_MODEL_PRODID_LEN;
    buf += OLA_DEV_MODEL_PRODID_LEN;

    *offset = tmpOffset;
    return OLA_OK;
}

static bool IsNegoTypeValid(OlaSecNegotiationType negoType)
{
    bool ret = false;
    OlaLogInfo("negoType[%d]", negoType);
    switch (negoType) {
        case OLA_SEC_NEGO_TYPE_SPEKE:
        case OLA_SEC_NEGO_TYPE_JPAKE:
        case OLA_SEC_NEGO_TYPE_PECDH:
            ret = true;
            break;
        default:
            OlaLogError("invalid nego type");
            break;
    }
    return ret;
}

static bool IsCodeCapValid(OlaDevCodeCap codeCap)
{
    bool ret = false;
    OlaLogInfo("codeCap[%d]", codeCap);
    switch (codeCap) {
        case OLA_DEV_CODE_CAP_PIN_CODE:
        case OLA_DEV_CODE_CAP_QR_CODE:
        case OLA_DEV_CODE_CAP_PIN_QR_BOTH:
            ret = true;
            break;
        default:
            OlaLogError("invalid codeCap type");
            break;
    }
    return ret;
}

static bool IsAuthTypeValid(OlaDevAuthType authType)
{
    bool ret = false;
    OlaLogInfo("authType[%d]", authType);
    switch (authType) {
        case OLA_DEV_AUTH_TYPE_CERT:
        case OLA_DEV_AUTH_TYPE_LICENSE:
            ret = true;
            break;
        default:
            OlaLogError("invalid auth type");
            break;
    }
    return ret;
}

static bool IsPinCodeTypeValid(OlaPinCodeType pinCodeType)
{
    bool ret = false;
    OlaLogInfo("pinCodeType[%d]", pinCodeType);
    switch (pinCodeType) {
        case OLA_PINCODE_TYPE_DEFAULT:
        case OLA_PINCODE_TYPE_OUTSIDE:
            ret = true;
            break;
        default:
            OlaLogError("invalid pinCode type");
            break;
    }
    return ret;
}

static int BuildSsidWithDevSecCap(char *ssid, unsigned int len, unsigned int *offset)
{
    if ((ssid == NULL) || (len == 0) || (offset == NULL) || (*offset >= len)) {
        OlaLogError("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }
    /* 获取协商能力拼装字符, Bit7 Bit0固定为0. */
    char tmpSecCap = 0;
    const OlaNetCfgParam *initPara = OlaGetNetCfgParam();
    if (initPara == NULL) {
        OlaLogError("get init param failed");
        return OLA_ERROR;
    }

    if ((!IsNegoTypeValid(initPara->negoType)) || (!IsCodeCapValid(initPara->codeCap)) ||
        (!IsAuthTypeValid(initPara->authType)) || (!IsPinCodeTypeValid(initPara->pinCodeType))) {
        OlaLogError("invalid sec cap");
        return OLA_ERROR;
    }
    /* Bit2-1: 安全协商方式00-SPEKE, 01-J-PAKE, 10-PIN+ECDH. */
    if (initPara->negoType == OLA_SEC_NEGO_TYPE_JPAKE) {
        tmpSecCap = tmpSecCap | SEC_CAP_MASK_NEGO_TYPE_JPAKE;
    } else if (initPara->negoType == OLA_SEC_NEGO_TYPE_PECDH) {
        tmpSecCap = tmpSecCap | SEC_CAP_MASK_NEGO_TYPE_PECDH;
    }

    /* Bit3: 应用终端认证方式 0 – 证书; 1 – License. */
    if (initPara->authType == OLA_DEV_AUTH_TYPE_LICENSE) {
        tmpSecCap = tmpSecCap | SEC_CAP_MASK_AUTH_TYPE_LISENCE;
    }

    /* Bit4: 默认PIN码格式, 0表示外部输入, 1表示默认PIN码. */
    if (initPara->pinCodeType == OLA_PINCODE_TYPE_DEFAULT) {
        tmpSecCap = tmpSecCap | SEC_CAP_MASK_DEFAULT_PIN_CODE;
    }

    /* Bit6-5: 01-PIN码, 10表示QR码, 11表示都支持. */
    switch (initPara->codeCap) {
        case OLA_DEV_CODE_CAP_PIN_CODE:
            tmpSecCap = tmpSecCap | SEC_CAP_MASK_CODE_TYPE_PIN;
            break;
        case OLA_DEV_CODE_CAP_QR_CODE:
            tmpSecCap = tmpSecCap | SEC_CAP_MASK_CODE_TYPE_QR;
            break;
        case OLA_DEV_CODE_CAP_PIN_QR_BOTH:
            tmpSecCap = tmpSecCap | SEC_CAP_MASK_CODE_TYPE_BOTH;
            break;
        default:
            break;
    }
    ssid[*offset] = tmpSecCap;
    *offset = *offset + 1;
    OlaLogInfo("ssid devSecCap: [%02x]", tmpSecCap);
    return OLA_OK;
}

/* 秘钥类终端必须携带 EE FF */
static int BuildSsidWithPreShareKey(char *ssid, unsigned int len, unsigned int *offset)
{
    if ((ssid == NULL) || (len == 0) || (offset == NULL) ||
        (len <= (*offset + SSID_ITEM_EE_LEN + SSID_ITEM_FF_LEN))) {
        OlaLogError("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }
    const OlaNetCfgParam *cfgParam = OlaGetNetCfgParam();
    if (cfgParam == NULL) {
        OlaLogError("get cfg parma failed");
        return OLA_ERROR;
    }
    /* 证书认证不需要填充EEFF字段 */
    if (cfgParam->authType == OLA_DEV_AUTH_TYPE_CERT) {
        OlaLogInfo("No need to add EEFF");
        return OLA_OK;
    }

    const OlaDeviceInfo *devInfo = OlaGetDevInfo();
    if (devInfo == NULL) {
        OlaLogError("get dev info failed");
        return OLA_ERROR;
    }
    if (strcpy_s(ssid + *offset, len - *offset, devInfo->platformId) != OLA_OK) {
        OlaLogError("fill platformId failed");
        return OLA_SECUREC_ERR_STRCPY;
    }
    *offset = *offset + SSID_ITEM_EE_LEN;

    unsigned char random = {0};
    if (OlaRand(&random, sizeof(unsigned char)) != OLA_OK) {
        OlaLogError("generate random failed");
        return OLA_ERROR;
    }
    OlaLogInfo("random: %02x", random);
    int ret = OlaHexify((unsigned char *)ssid + *offset, (len - *offset), &random,
        sizeof(unsigned char), false);
    if (ret != OLA_OK) {
        OlaLogWarning("hexify wrong");
        return ret;
    }
    *offset = *offset + SSID_ITEM_FF_LEN;

    return OLA_OK;
}

static int BuildSsidWithDeivceInfo(char *ssid, unsigned int len, unsigned int *offset)
{
    if ((ssid == NULL) || (offset == NULL) || (len == 0) || (len < MAX_SOFTAP_SSID_DEV_LEN) || (*offset >= len)) {
        OlaLogError("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    /* 获取设备信息模型 */
    const OlaDeviceInfo *devModel = OlaGetDevInfo();
    if (devModel == NULL) {
        OlaLogError("get dev or manu info failed");
        return OLA_ERROR;
    }

    /* 使用设备信息以及厂家信息填充 */
    if (BuildSsidWithDevInfoInner(ssid, len, offset, devModel) != OLA_OK) {
        OlaLogError("build ssid failed");
        return OLA_ERROR;
    }

    /* 使用设备协商能力填充ssid */
    if (BuildSsidWithDevSecCap(ssid, len, offset) != OLA_OK) {
        OlaLogError("fill ssid with sec cap failed");
        return OLA_ERROR;
    }

    if (BuildSsidWithPreShareKey(ssid, len, offset) != OLA_OK) {
        OlaLogError("fill ssid with preshart failed");
        return OLA_ERROR;
    }
    return OLA_OK;
}

static int BuildSsidWithCustomData(char *ssid, unsigned int len, unsigned int *offset)
{
    if ((ssid == NULL) || (len == 0) || (offset == NULL) || (len <= *offset)) {
        OlaLogError("invalid param, len[%u]", len);
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }

    const OlaNetCfgParam *initPara = OlaGetNetCfgParam();
    if (initPara == NULL) {
        OlaLogError("get init param failed");
        return OLA_ERROR;
    }
    if ((initPara->customSsidData == NULL) || (strlen(initPara->customSsidData) == 0)) {
        OlaLogInfo("custom ssid data null");
        return OLA_OK;
    }

    unsigned int custLen = strlen(initPara->customSsidData);
    if (!IsStrInvalid(initPara->customSsidData, MIN_CUSTOM_SSID_DATA_LEN, MAX_CUSTOM_SSID_DATA_LEN)) {
        OlaLogInfo("custom ssid data invalid, len[%u]", custLen);
        return OLA_ERROR;
    }

    unsigned int remainLen = len - *offset;
    unsigned int cpyLen = remainLen <= custLen ? remainLen - 1 : custLen;
    if (strncpy_s(ssid + *offset, remainLen, initPara->customSsidData, cpyLen) != OLA_OK) {
        OlaLogError("copy custom ssid data failed");
        return OLA_SECUREC_ERR_STRNCPY;
    }
    *offset = *offset + cpyLen;
    return OLA_OK;
}

/* 通过固定字符、设备信息以及厂家自定义数据构成ssid */
static int BuildSoftApSsid(char *ssid, unsigned int len)
{
    if ((ssid == NULL) || (len == 0) || (len < MIN_SOFTAP_SSID_LEN)) {
        OlaLogError("invalid param");
        return OLA_COM_ERR_INVALID_ARGUMENT;
    }
    int offset = snprintf_s(ssid, len, len - 1, "%s%s", OLA_SSID_PREFIX, OLA_SSID_VERSION);
    if (offset != OLA_PREFIX_VERSION_LEN) {
        OlaLogError("get prefix and version failed, offset = %d", offset);
        return OLA_ERROR;
    }

    if (BuildSsidWithDeivceInfo(ssid, len, (unsigned int *)(&offset)) != OLA_OK) {
        OlaLogError("build ssid with device model failed");
        return OLA_ERROR;
    }

    /* 厂商自定义ssid末尾数据 */
    if (BuildSsidWithCustomData(ssid, len, (unsigned int *)(&offset)) != OLA_OK) {
        OlaLogError("build ssid with custom data failed");
        return OLA_ERROR;
    }

    return OLA_OK;
}

static int InitProTime(void)
{
    const OlaNetCfgParam *initPara = OlaGetNetCfgParam();
    if (initPara == NULL) {
        OlaLogError("get init param failed");
        return OLA_ERROR;
    }
    if (initPara->timeout == 0) {
        OlaLogError("get netcfg timeout failed");
        return OLA_ERROR;
    }
    /* 外部参数以秒为单位 */
    g_proTimeout = initPara->timeout * MS_PER_SECOND;

    if (g_proStartTime == 0) {
        if (OlaGetTime(&g_proStartTime) != OLA_OK) {
            OlaLogError("get dev time failed");
            return OLA_OS_ERR_GET_TIME;
        }
    }
    return OLA_OK;
}

/* softap配网初始化 */
static int SoftapProInit(void)
{
    g_negoHook = OlaGetNegotiationHooks();
    if (g_negoHook == NULL) {
        OlaLogError("get nego hook failed");
        return OLA_NEGO_ERR_HOOK_NULL;
    }
    if ((g_negoHook->getStatus != NULL) && (g_negoHook->getStatus() == OLA_NEGO_STATUS_UNINIT)) {
        if (g_negoHook->init(&g_negoCb) != OLA_OK) {
            OlaLogError("nego hooks init failed");
            return OLA_ERROR;
        }
    }

    /* 组建softap的ssid */
    char ssid[MAX_SOFTAP_SSID_LEN + 1] = {0};
    if (BuildSoftApSsid(ssid, MAX_SOFTAP_SSID_LEN + 1) != OLA_OK) {
        OlaLogError("build softap ssid failed");
        return OLA_ERROR;
    }

    /* 启动softap, 等待配网 */
    int ret = OlaSoftApStart(ssid, strlen(ssid));
    if (ret != OLA_OK) {
        OlaLogError("start softap failed, ret = %d", ret);
        return OLA_ERROR;
    }

    g_provisioningCtx = OlaCoapNewContext(NULL, PROVISIONING_COAP_PROT, OLA_COAP_PROTOCOL_TYPE_UDP);
    if (g_provisioningCtx == NULL) {
        OlaLogError("get coap context failed");
        return OLA_ERROR;
    }
    if (SetProServicesHandler() != OLA_OK) {
        OlaLogError("set coap services uri handler failed");
        return OLA_ERROR;
    }

    if (InitProTime() != OLA_OK) {
        OlaLogError("init provisiniong time failed");
        return OLA_ERROR;
    }

    /* 通知外部配网开始 */
    OlaLogInfo("provisioning start");
    OlaNotifySdkEvent(OLA_EVENT_CODE_NETCFG_START);
    return OLA_OK;
}

static void ClearSoftapProSettings(void)
{
    g_proStartTime = 0;
    /* 清除设置, 协商、coap */
    if ((g_negoHook != NULL)) {
        g_negoHook->deinit();
        g_negoHook = NULL;
    }

    /* 释放coap资源释放检查，超时退出 */
    if (g_provisioningCtx != NULL) {
        int retryTimes = 0;
        while (!OlaIsCoapCanExit(g_provisioningCtx) && (retryTimes < MAX_RELEASE_COAP_SOURCES_RETRY_TIMES)) {
            /* coap处理需要保证100ms调用一次，等待时间为此处timeout入参和外层状态机休眠时间 */
            (void)OlaCoapProcess(DEVICE_PROVISIONING_COAP_TIMEVAL);
            retryTimes++;
            TaskWait();
        }
        if (OlaCoapFreeContext(g_provisioningCtx) != OLA_OK) {
            OlaLogError("free coap context failed");
        }
        g_provisioningCtx = NULL;
    }
    g_negoSession = NULL;
    g_reqId = 0;
    /* 关闭softap */
    if (OlaSoftApStop() != OLA_OK) {
        OlaLogError("stop softap failed");
    }
}

static int SoftapProWorking(void)
{
    unsigned long long now = 0;

    if (OlaGetTime(&now) != OLA_OK) {
        OlaLogError("get time failed");
        return OLA_OS_ERR_GET_TIME;
    }

    /* 检查配网是否超时 */
    if ((g_proStartTime != 0) && (now - g_proStartTime > g_proTimeout)) {
        OlaLogError("provisioning timeout");
        OlaSetProvisioningState(OLA_SDK_SOFTAP_PRO_STATE_TIMEOUT);
        return OLA_STATE_RUNING;
    }
    /* 接收处理报文 */
    int ret = OlaCoapProcess(DEVICE_PROVISIONING_COAP_TIMEVAL);
    if (ret != OLA_OK) {
        OlaLogError("coap process failed, ret[%d]", ret);
        return OLA_ERROR;
    }
    return OLA_STATE_RUNING;
}

static int SoftapProWorkDone(void)
{
    /* 通知外部配网成功 */
    OlaLogNotice("provisioning done");
    OlaNotifySdkEvent(OLA_EVENT_CODE_NETCFG_SUCCESS);

    ClearSoftapProSettings();

    return OLA_OK;
}

static int SoftapProTimeout(void)
{
    OlaLogNotice("provisioning timeout, waiting reset");
    OlaNotifySdkEvent(OLA_EVENT_CODE_NETCFG_TIMEOUT);
    ClearSoftapProSettings();
    while (g_isSoftapProRunning) {
        OlaSleepMs(TIMEOUT_SLEEP_MS);
    }
    return OLA_OK;
}

static int SoftapProFailed(void)
{
    OlaLogNotice("provisioning failed");
    /* 清除设置, 协商、coap、配网信息 */
    ClearSoftapProSettings();
    OlaNotifySdkEvent(OLA_EVENT_CODE_NETCFG_FAIL);
    return OLA_OK;
}

static int SoftapProStateMachine(void)
{
    int ret = OLA_STATE_RUNING;
    switch (OlaGetProvisioningState()) {
        case OLA_SDK_SOFTAP_PRO_STATE_INIT: {
            if (SoftapProInit() == OLA_OK) {
                OlaSetProvisioningState(OLA_SDK_SOFTAP_PRO_STATE_WORKING);
            } else {
                OlaLogError("provisioning init failed");
                OlaSetProvisioningState(OLA_SDK_SOFTAP_PRO_STATE_FAILED);
            }
            break;
        }
        case OLA_SDK_SOFTAP_PRO_STATE_WORKING:
            if (SoftapProWorking() == OLA_ERROR) {
                OlaLogError("provisioning working failed");
                OlaSetProvisioningState(OLA_SDK_SOFTAP_PRO_STATE_FAILED);
            }
            break;
        case OLA_SDK_SOFTAP_PRO_STATE_WORKDONE:
            if (SoftapProWorkDone() == OLA_OK) {
                ret = OLA_OK;
            } else {
                OlaLogError("provisioning done failed");
                OlaSetProvisioningState(OLA_SDK_SOFTAP_PRO_STATE_FAILED);
            }
            break;
        case OLA_SDK_SOFTAP_PRO_STATE_TIMEOUT:
            if (SoftapProTimeout() != OLA_OK) {
                OlaLogError("provisioning timeout process failed");
                break;
            }
            break;
        case OLA_SDK_SOFTAP_PRO_STATE_FAILED:
            (void)SoftapProFailed();
            ret = OLA_ERROR;
            break;
        default:
            break;
    }
    return ret;
}

static int SoftapProStart(void)
{
    g_isSoftapProRunning = true;
    OlaSetProvisioningState(OLA_SDK_SOFTAP_PRO_STATE_INIT);
    while (g_isSoftapProRunning) {
        int ret = SoftapProStateMachine();
        if (ret == OLA_OK) {
            /* 返回OK时，配网结束状态已经释放资源 */
            OlaLogInfo("softap provisining done");
            return ret;
        } else if (ret == OLA_ERROR) {
            /* 返回Error时，配网失败状态已经释放资源 */
            OlaLogError("softap provisioning failed");
            return ret;
        }
        TaskWait();
    }
    /* 由外部停止配网状态，需要释放资源 */
    ClearSoftapProSettings();
    return OLA_OK;
}

static int SoftapProStop(void)
{
    g_isSoftapProRunning = false;
    OlaLogInfo("softap provisioning exit");

    return OLA_OK;
}

static OlaProvisioningHooks g_softapProHooks = {
    .start = SoftapProStart,
    .stop = SoftapProStop,
};

const OlaProvisioningHooks *OlaProGetSoftapHooks(void)
{
    return &g_softapProHooks;
}